home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 151 / cd-rom 151.iso / internet / firefox / Firefox Setup 3.0 Beta 1.exe / nonlocalized / components / nsSessionStore.js < prev    next >
Encoding:
JavaScript  |  2007-11-09  |  71.5 KB  |  2,130 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is the nsSessionStore component.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Simon B├╝nzli <zeniko@gmail.com>
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Dietrich Ayala <autonome@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. /**
  39.  * Session Storage and Restoration
  40.  * 
  41.  * Overview
  42.  * This service keeps track of a user's session, storing the various bits
  43.  * required to return the browser to it's current state. The relevant data is 
  44.  * stored in memory, and is periodically saved to disk in a file in the 
  45.  * profile directory. The service is started at first window load, in
  46.  * delayedStartup, and will restore the session from the data received from
  47.  * the nsSessionStartup service.
  48.  */
  49.  
  50. /* :::::::: Constants and Helpers ::::::::::::::: */
  51.  
  52. const Cc = Components.classes;
  53. const Ci = Components.interfaces;
  54. const Cr = Components.results;
  55. const Cu = Components.utils;
  56.  
  57. const CID = Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}");
  58. const CONTRACT_ID = "@mozilla.org/browser/sessionstore;1";
  59. const CLASS_NAME = "Browser Session Store Service";
  60.  
  61. const STATE_STOPPED = 0;
  62. const STATE_RUNNING = 1;
  63. const STATE_QUITTING = -1;
  64.  
  65. const STATE_STOPPED_STR = "stopped";
  66. const STATE_RUNNING_STR = "running";
  67.  
  68. const PRIVACY_NONE = 0;
  69. const PRIVACY_ENCRYPTED = 1;
  70. const PRIVACY_FULL = 2;
  71.  
  72. // global notifications observed
  73. const OBSERVING = [
  74.   "domwindowopened", "domwindowclosed",
  75.   "quit-application-requested", "quit-application-granted",
  76.   "quit-application", "browser:purge-session-history"
  77. ];
  78.  
  79. /*
  80. XUL Window properties to (re)store
  81. Restored in restoreDimensions()
  82. */
  83. const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
  84.  
  85. /* 
  86. Hideable window features to (re)store
  87. Restored in restoreWindowFeatures()
  88. */
  89. const WINDOW_HIDEABLE_FEATURES = [
  90.   "menubar", "toolbar", "locationbar", 
  91.   "personalbar", "statusbar", "scrollbars"
  92. ];
  93.  
  94. /*
  95. docShell capabilities to (re)store
  96. Restored in restoreHistory()
  97. eg: browser.docShell["allow" + aCapability] = false;
  98. */
  99. const CAPABILITIES = [
  100.   "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images"
  101. ];
  102.  
  103. // module for JSON conversion (needed for the nsISessionStore API)
  104. Cu.import("resource://gre/modules/JSON.jsm");
  105.  
  106. function debug(aMsg) {
  107.   aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
  108.   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
  109.                                      .logStringMessage(aMsg);
  110. }
  111.  
  112. /* :::::::: The Service ::::::::::::::: */
  113.  
  114. function SessionStoreService() {
  115. }
  116.  
  117. SessionStoreService.prototype = {
  118.  
  119.   // xul:tab attributes to (re)store (extensions might want to hook in here)
  120.   xulAttributes: [],
  121.  
  122.   // set default load state
  123.   _loadState: STATE_STOPPED,
  124.  
  125.   // minimal interval between two save operations (in milliseconds)
  126.   _interval: 10000,
  127.  
  128.   // when crash recovery is disabled, session data is not written to disk
  129.   _resume_from_crash: true,
  130.  
  131.   // time in milliseconds (Date.now()) when the session was last written to file
  132.   _lastSaveTime: 0, 
  133.  
  134.   // states for all currently opened windows
  135.   _windows: {},
  136.  
  137.   // in case the last closed window ain't a navigator:browser one
  138.   _lastWindowClosed: null,
  139.  
  140.   // not-"dirty" windows usually don't need to have their data updated
  141.   _dirtyWindows: {},
  142.  
  143.   // flag all windows as dirty
  144.   _dirty: false,
  145.  
  146. /* ........ Global Event Handlers .............. */
  147.  
  148.   /**
  149.    * Initialize the component
  150.    */
  151.   init: function sss_init(aWindow) {
  152.     if (!aWindow || this._loadState == STATE_RUNNING) {
  153.       // make sure that all browser windows which try to initialize
  154.       // SessionStore are really tracked by it
  155.       if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  156.         this.onLoad(aWindow);
  157.       return;
  158.     }
  159.  
  160.     this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  161.                        getService(Ci.nsIPrefService).getBranch("browser.");
  162.     this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  163.  
  164.     // if the service is disabled, do not init 
  165.     if (!this._prefBranch.getBoolPref("sessionstore.enabled"))
  166.       return;
  167.  
  168.     var observerService = Cc["@mozilla.org/observer-service;1"].
  169.                           getService(Ci.nsIObserverService);
  170.  
  171.     OBSERVING.forEach(function(aTopic) {
  172.       observerService.addObserver(this, aTopic, true);
  173.     }, this);
  174.     
  175.     // get interval from prefs - used often, so caching/observing instead of fetching on-demand
  176.     this._interval = this._prefBranch.getIntPref("sessionstore.interval");
  177.     this._prefBranch.addObserver("sessionstore.interval", this, true);
  178.     
  179.     // get crash recovery state from prefs and allow for proper reaction to state changes
  180.     this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
  181.     this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
  182.     
  183.     // observe prefs changes so we can modify stored data to match
  184.     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
  185.  
  186.     // get file references
  187.     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  188.                      getService(Ci.nsIProperties);
  189.     this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile);
  190.     this._sessionFileBackup = this._sessionFile.clone();
  191.     this._sessionFile.append("sessionstore.js");
  192.     this._sessionFileBackup.append("sessionstore.bak");
  193.    
  194.     // get string containing session state
  195.     var iniString;
  196.     try {
  197.       var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
  198.                getService(Ci.nsISessionStartup);
  199.       if (ss.doRestore())
  200.         iniString = ss.state;
  201.     }
  202.     catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
  203.  
  204.     if (iniString) {
  205.       try {
  206.         // parse the session state into JS objects
  207.         this._initialState = this._safeEval(iniString);
  208.         // set bool detecting crash
  209.         this._lastSessionCrashed =
  210.           this._initialState.session && this._initialState.session.state &&
  211.           this._initialState.session.state == STATE_RUNNING_STR;
  212.         
  213.         // restore the features of the first window from localstore.rdf
  214.         WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  215.           delete this._initialState.windows[0][aAttr];
  216.         }, this);
  217.         delete this._initialState.windows[0].hidden;
  218.       }
  219.       catch (ex) { debug("The session file is invalid: " + ex); }
  220.     }
  221.     
  222.     // if last session crashed, backup the session
  223.     if (this._lastSessionCrashed) {
  224.       try {
  225.         this._writeFile(this._sessionFileBackup, iniString);
  226.       }
  227.       catch (ex) { } // nothing else we can do here
  228.     }
  229.  
  230.     // remove the session data files if crash recovery is disabled
  231.     if (!this._resume_from_crash)
  232.       this._clearDisk();
  233.     
  234.     // As this is called at delayedStartup, restoration must be initiated here
  235.     this.onLoad(aWindow);
  236.   },
  237.  
  238.   /**
  239.    * Called on application shutdown, after notifications:
  240.    * quit-application-granted, quit-application
  241.    */
  242.   _uninit: function sss_uninit() {
  243.     if (this._doResumeSession()) { // save all data for session resuming 
  244.       this.saveState(true);
  245.     }
  246.     else { // discard all session related data 
  247.       this._clearDisk();
  248.     }
  249.     // Make sure to break our cycle with the save timer
  250.     if (this._saveTimer) {
  251.       this._saveTimer.cancel();
  252.       this._saveTimer = null;
  253.     }
  254.   },
  255.  
  256.   /**
  257.    * Handle notifications
  258.    */
  259.   observe: function sss_observe(aSubject, aTopic, aData) {
  260.     // for event listeners
  261.     var _this = this;
  262.  
  263.     switch (aTopic) {
  264.     case "domwindowopened": // catch new windows
  265.       aSubject.addEventListener("load", function(aEvent) {
  266.         aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
  267.         _this.onLoad(aEvent.currentTarget);
  268.         }, false);
  269.       break;
  270.     case "domwindowclosed": // catch closed windows
  271.       this.onClose(aSubject);
  272.       break;
  273.     case "quit-application-requested":
  274.       // get a current snapshot of all windows
  275.       this._forEachBrowserWindow(function(aWindow) {
  276.         this._collectWindowData(aWindow);
  277.       });
  278.       this._dirtyWindows = [];
  279.       this._dirty = false;
  280.       break;
  281.     case "quit-application-granted":
  282.       // freeze the data at what we've got (ignoring closing windows)
  283.       this._loadState = STATE_QUITTING;
  284.       break;
  285.     case "quit-application":
  286.       if (aData == "restart")
  287.         this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
  288.       this._loadState = STATE_QUITTING; // just to be sure
  289.       this._uninit();
  290.       break;
  291.     case "browser:purge-session-history": // catch sanitization 
  292.       this._forEachBrowserWindow(function(aWindow) {
  293.         Array.forEach(aWindow.getBrowser().browsers, function(aBrowser) {
  294.           delete aBrowser.parentNode.__SS_data;
  295.         });
  296.       });
  297.       this._lastWindowClosed = null;
  298.       this._clearDisk();
  299.       // also clear all data about closed tabs
  300.       for (ix in this._windows) {
  301.         this._windows[ix]._closedTabs = [];
  302.       }
  303.       // give the tabbrowsers a chance to clear their histories first
  304.       var win = this._getMostRecentBrowserWindow();
  305.       if (win)
  306.         win.setTimeout(function() { _this.saveState(true); }, 0);
  307.       else
  308.         this.saveState(true);
  309.       break;
  310.     case "nsPref:changed": // catch pref changes
  311.       switch (aData) {
  312.       // if the user decreases the max number of closed tabs they want
  313.       // preserved update our internal states to match that max
  314.       case "sessionstore.max_tabs_undo":
  315.         var ix;
  316.         for (ix in this._windows) {
  317.           this._windows[ix]._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
  318.         }
  319.         break;
  320.       case "sessionstore.interval":
  321.         this._interval = this._prefBranch.getIntPref("sessionstore.interval");
  322.         // reset timer and save
  323.         if (this._saveTimer) {
  324.           this._saveTimer.cancel();
  325.           this._saveTimer = null;
  326.         }
  327.         this.saveStateDelayed(null, -1);
  328.         break;
  329.       case "sessionstore.resume_from_crash":
  330.         this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
  331.         // either create the file with crash recovery information or remove it
  332.         // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
  333.         if (this._resume_from_crash)
  334.           this.saveState(true);
  335.         else if (this._loadState == STATE_RUNNING)
  336.           this._clearDisk();
  337.         break;
  338.       }
  339.       break;
  340.     case "timer-callback": // timer call back for delayed saving
  341.       this._saveTimer = null;
  342.       this.saveState();
  343.       break;
  344.     }
  345.   },
  346.  
  347. /* ........ Window Event Handlers .............. */
  348.  
  349.   /**
  350.    * Implement nsIDOMEventListener for handling various window and tab events
  351.    */
  352.   handleEvent: function sss_handleEvent(aEvent) {
  353.     switch (aEvent.type) {
  354.       case "load":
  355.       case "pageshow":
  356.         this.onTabLoad(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
  357.         break;
  358.       case "input":
  359.       case "DOMAutoComplete":
  360.         this.onTabInput(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
  361.         break;
  362.       case "TabOpen":
  363.       case "TabClose":
  364.         var panelID = aEvent.originalTarget.linkedPanel;
  365.         var tabpanel = aEvent.originalTarget.ownerDocument.getElementById(panelID);
  366.         if (aEvent.type == "TabOpen") {
  367.           this.onTabAdd(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
  368.         }
  369.         else {
  370.           this.onTabClose(aEvent.currentTarget.ownerDocument.defaultView, aEvent.originalTarget);
  371.           this.onTabRemove(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
  372.         }
  373.         break;
  374.       case "TabSelect":
  375.         var tabpanels = aEvent.currentTarget.mPanelContainer;
  376.         this.onTabSelect(aEvent.currentTarget.ownerDocument.defaultView, tabpanels);
  377.         break;
  378.     }
  379.   },
  380.  
  381.   /**
  382.    * If it's the first window load since app start...
  383.    * - determine if we're reloading after a crash or a forced-restart
  384.    * - restore window state
  385.    * - restart downloads
  386.    * Set up event listeners for this window's tabs
  387.    * @param aWindow
  388.    *        Window reference
  389.    */
  390.   onLoad: function sss_onLoad(aWindow) {
  391.     // return if window has already been initialized
  392.     if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
  393.       return;
  394.  
  395.     // ignore non-browser windows and windows opened while shutting down
  396.     if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
  397.       this._loadState == STATE_QUITTING)
  398.       return;
  399.  
  400.     // assign it a unique identifier (timestamp)
  401.     aWindow.__SSi = "window" + Date.now();
  402.  
  403.     // and create its data object
  404.     this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] };
  405.     
  406.     // perform additional initialization when the first window is loading
  407.     if (this._loadState == STATE_STOPPED) {
  408.       this._loadState = STATE_RUNNING;
  409.       this._lastSaveTime = Date.now();
  410.       
  411.       // don't save during the first ten seconds
  412.       // (until most of the pages have been restored)
  413.       this.saveStateDelayed(aWindow, 10000);
  414.  
  415.       // restore a crashed session resp. resume the last session if requested
  416.       if (this._initialState) {
  417.         // make sure that the restored tabs are first in the window
  418.         this._initialState._firstTabs = true;
  419.         this.restoreWindow(aWindow, this._initialState, this._isCmdLineEmpty(aWindow));
  420.         delete this._initialState;
  421.       }
  422.       
  423.       if (this._lastSessionCrashed) {
  424.         // restart any interrupted downloads
  425.         aWindow.setTimeout(this.retryDownloads, 0);
  426.       }
  427.     }
  428.     
  429.     var tabbrowser = aWindow.getBrowser();
  430.     var tabpanels = tabbrowser.mPanelContainer;
  431.     
  432.     // add tab change listeners to all already existing tabs
  433.     for (var i = 0; i < tabpanels.childNodes.length; i++) {
  434.       this.onTabAdd(aWindow, tabpanels.childNodes[i], true);
  435.     }
  436.     // notification of tab add/remove/selection
  437.     tabbrowser.addEventListener("TabOpen", this, true);
  438.     tabbrowser.addEventListener("TabClose", this, true);
  439.     tabbrowser.addEventListener("TabSelect", this, true);
  440.   },
  441.  
  442.   /**
  443.    * On window close...
  444.    * - remove event listeners from tabs
  445.    * - save all window data
  446.    * @param aWindow
  447.    *        Window reference
  448.    */
  449.   onClose: function sss_onClose(aWindow) {
  450.     // ignore windows not tracked by SessionStore
  451.     if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
  452.       return;
  453.     }
  454.     
  455.     if (this.windowToFocus && this.windowToFocus == aWindow) {
  456.       delete this.windowToFocus;
  457.     }
  458.     
  459.     var tabbrowser = aWindow.getBrowser();
  460.     var tabpanels = tabbrowser.mPanelContainer;
  461.  
  462.     tabbrowser.removeEventListener("TabOpen", this, true);
  463.     tabbrowser.removeEventListener("TabClose", this, true);
  464.     tabbrowser.removeEventListener("TabSelect", this, true);
  465.     
  466.     for (var i = 0; i < tabpanels.childNodes.length; i++) {
  467.       this.onTabRemove(aWindow, tabpanels.childNodes[i], true);
  468.     }
  469.     
  470.     if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down 
  471.       // update all window data for a last time
  472.       this._collectWindowData(aWindow);
  473.       
  474.       // preserve this window's data (in case it was the last navigator:browser)
  475.       this._lastWindowClosed = this._windows[aWindow.__SSi];
  476.       this._lastWindowClosed.title = aWindow.content.document.title;
  477.       this._updateCookies([this._lastWindowClosed]);
  478.       
  479.       // clear this window from the list
  480.       delete this._windows[aWindow.__SSi];
  481.       
  482.       // save the state without this window to disk
  483.       this.saveStateDelayed();
  484.     }
  485.     
  486.     // cache the window state until the window is completely gone
  487.     aWindow.__SS_dyingCache = this._windows[aWindow.__SSi] || this._lastWindowClosed;
  488.     
  489.     delete aWindow.__SSi;
  490.   },
  491.  
  492.   /**
  493.    * set up listeners for a new tab
  494.    * @param aWindow
  495.    *        Window reference
  496.    * @param aPanel
  497.    *        TabPanel reference
  498.    * @param aNoNotification
  499.    *        bool Do not save state if we're updating an existing tab
  500.    */
  501.   onTabAdd: function sss_onTabAdd(aWindow, aPanel, aNoNotification) {
  502.     aPanel.addEventListener("load", this, true);
  503.     aPanel.addEventListener("pageshow", this, true);
  504.     aPanel.addEventListener("input", this, true);
  505.     aPanel.addEventListener("DOMAutoComplete", this, true);
  506.     
  507.     if (!aNoNotification) {
  508.       this.saveStateDelayed(aWindow);
  509.     }
  510.   },
  511.  
  512.   /**
  513.    * remove listeners for a tab
  514.    * @param aWindow
  515.    *        Window reference
  516.    * @param aPanel
  517.    *        TabPanel reference
  518.    * @param aNoNotification
  519.    *        bool Do not save state if we're updating an existing tab
  520.    */
  521.   onTabRemove: function sss_onTabRemove(aWindow, aPanel, aNoNotification) {
  522.     aPanel.removeEventListener("load", this, true);
  523.     aPanel.removeEventListener("pageshow", this, true);
  524.     aPanel.removeEventListener("input", this, true);
  525.     aPanel.removeEventListener("DOMAutoComplete", this, true);
  526.     
  527.     delete aPanel.__SS_data;
  528.     delete aPanel.__SS_text;
  529.     
  530.     if (!aNoNotification) {
  531.       this.saveStateDelayed(aWindow);
  532.     }
  533.   },
  534.  
  535.   /**
  536.    * When a tab closes, collect it's properties
  537.    * @param aWindow
  538.    *        Window reference
  539.    * @param aTab
  540.    *        TabPanel reference
  541.    */
  542.   onTabClose: function sss_onTabClose(aWindow, aTab) {
  543.     var maxTabsUndo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
  544.     // don't update our internal state if we don't have to
  545.     if (maxTabsUndo == 0) {
  546.       return;
  547.     }
  548.     
  549.     // make sure that the tab related data is up-to-date
  550.     this._saveWindowHistory(aWindow);
  551.     this._updateTextAndScrollData(aWindow);
  552.     
  553.     // store closed-tab data for undo
  554.     var tabState = this._windows[aWindow.__SSi].tabs[aTab._tPos];
  555.     if (tabState && (tabState.entries.length > 1 ||
  556.         tabState.entries[0].url != "about:blank")) {
  557.       this._windows[aWindow.__SSi]._closedTabs.unshift({
  558.         state: tabState,
  559.         title: aTab.getAttribute("label"),
  560.         image: aTab.getAttribute("image"),
  561.         pos: aTab._tPos
  562.       });
  563.       var length = this._windows[aWindow.__SSi]._closedTabs.length;
  564.       if (length > maxTabsUndo)
  565.         this._windows[aWindow.__SSi]._closedTabs.splice(maxTabsUndo, length - maxTabsUndo);
  566.     }
  567.   },
  568.  
  569.   /**
  570.    * When a tab loads, save state.
  571.    * @param aWindow
  572.    *        Window reference
  573.    * @param aPanel
  574.    *        TabPanel reference
  575.    * @param aEvent
  576.    *        Event obj
  577.    */
  578.   onTabLoad: function sss_onTabLoad(aWindow, aPanel, aEvent) { 
  579.     // react on "load" and solitary "pageshow" events (the first "pageshow"
  580.     // following "load" is too late for deleting the data caches)
  581.     if (aEvent.type != "load" && !aEvent.persisted) {
  582.       return;
  583.     }
  584.     
  585.     delete aPanel.__SS_data;
  586.     delete aPanel.__SS_text;
  587.     this.saveStateDelayed(aWindow);
  588.     
  589.     // attempt to update the current URL we send in a crash report
  590.     this._updateCrashReportURL(aWindow);
  591.   },
  592.  
  593.   /**
  594.    * Called when a tabpanel sends the "input" notification 
  595.    * stores textarea data
  596.    * @param aWindow
  597.    *        Window reference
  598.    * @param aPanel
  599.    *        TabPanel reference
  600.    * @param aEvent
  601.    *        Event obj
  602.    */
  603.   onTabInput: function sss_onTabInput(aWindow, aPanel, aEvent) {
  604.     if (this._saveTextData(aPanel, aEvent.originalTarget)) {
  605.       this.saveStateDelayed(aWindow, 3000);
  606.     }
  607.   },
  608.  
  609.   /**
  610.    * When a tab is selected, save session data
  611.    * @param aWindow
  612.    *        Window reference
  613.    * @param aPanels
  614.    *        TabPanel reference
  615.    */
  616.   onTabSelect: function sss_onTabSelect(aWindow, aPanels) {
  617.     if (this._loadState == STATE_RUNNING) {
  618.       this._windows[aWindow.__SSi].selected = aPanels.selectedIndex;
  619.       this.saveStateDelayed(aWindow);
  620.  
  621.       // attempt to update the current URL we send in a crash report
  622.       this._updateCrashReportURL(aWindow);
  623.     }
  624.   },
  625.  
  626. /* ........ nsISessionStore API .............. */
  627.  
  628.   getBrowserState: function sss_getBrowserState() {
  629.     return this._toJSONString(this._getCurrentState());
  630.   },
  631.  
  632.   setBrowserState: function sss_setBrowserState(aState) {
  633.     var window = this._getMostRecentBrowserWindow();
  634.     if (!window) {
  635.       this._openWindowWithState("(" + aState + ")");
  636.       return;
  637.     }
  638.  
  639.     // close all other browser windows
  640.     this._forEachBrowserWindow(function(aWindow) {
  641.       if (aWindow != window) {
  642.         aWindow.close();
  643.       }
  644.     });
  645.  
  646.     // restore to the given state
  647.     this.restoreWindow(window, "(" + aState + ")", true);
  648.   },
  649.  
  650.   getWindowState: function sss_getWindowState(aWindow) {
  651.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  652.       return this._toJSONString({ windows: [aWindow.__SS_dyingCache] });
  653.     
  654.     return this._toJSONString(this._getWindowState(aWindow));
  655.   },
  656.  
  657.   setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) {
  658.     this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite);
  659.   },
  660.  
  661.   getClosedTabCount: function sss_getClosedTabCount(aWindow) {
  662.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  663.       return aWindow.__SS_dyingCache._closedTabs.length;
  664.     if (!aWindow.__SSi)
  665.       return 0; // not a browser window, or not otherwise tracked by SS.
  666.     
  667.     return this._windows[aWindow.__SSi]._closedTabs.length;
  668.   },
  669.  
  670.   closedTabNameAt: function sss_closedTabNameAt(aWindow, aIx) {
  671.     var tabs;
  672.     
  673.     if (aWindow.__SSi && aWindow.__SSi in this._windows)
  674.       tabs = this._windows[aWindow.__SSi]._closedTabs;
  675.     else if (aWindow.__SS_dyingCache)
  676.       tabs = aWindow.__SS_dyingCache._closedTabs;
  677.     else
  678.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  679.     
  680.     return tabs && aIx in tabs ? tabs[aIx].title : null;
  681.   },
  682.  
  683.   getClosedTabData: function sss_getClosedTabDataAt(aWindow) {
  684.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  685.       return this._toJSONString(aWindow.__SS_dyingCache._closedTabs);
  686.     
  687.     return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
  688.   },
  689.  
  690.   undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) {
  691.     var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
  692.  
  693.     // default to the most-recently closed tab
  694.     aIndex = aIndex || 0;
  695.  
  696.     if (aIndex in closedTabs) {
  697.       var browser = aWindow.getBrowser();
  698.  
  699.       // fetch the data of closed tab, while removing it from the array
  700.       var closedTab = closedTabs.splice(aIndex, 1).shift();
  701.       var closedTabState = closedTab.state;
  702.  
  703.       // create a new tab
  704.       closedTabState._tab = browser.addTab();
  705.         
  706.       // restore the tab's position
  707.       browser.moveTabTo(closedTabState._tab, closedTab.pos);
  708.   
  709.       // restore tab content
  710.       this.restoreHistoryPrecursor(aWindow, [closedTabState], 1, 0, 0);
  711.  
  712.       // focus the tab's content area
  713.       var content = browser.getBrowserForTab(closedTabState._tab).contentWindow;
  714.       aWindow.setTimeout(function() { content.focus(); }, 0);
  715.     }
  716.     else {
  717.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  718.     }
  719.   },
  720.  
  721.   getWindowValue: function sss_getWindowValue(aWindow, aKey) {
  722.     if (aWindow.__SSi) {
  723.       var data = this._windows[aWindow.__SSi].extData || {};
  724.       return data[aKey] || "";
  725.     }
  726.     else if (aWindow.__SS_dyingCache) {
  727.       data = aWindow.__SS_dyingCache.extData || {};
  728.       return data[aKey] || "";
  729.     }
  730.     else {
  731.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  732.     }
  733.   },
  734.  
  735.   setWindowValue: function sss_setWindowValue(aWindow, aKey, aStringValue) {
  736.     if (aWindow.__SSi) {
  737.       if (!this._windows[aWindow.__SSi].extData) {
  738.         this._windows[aWindow.__SSi].extData = {};
  739.       }
  740.       this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
  741.       this.saveStateDelayed(aWindow);
  742.     }
  743.     else {
  744.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  745.     }
  746.   },
  747.  
  748.   deleteWindowValue: function sss_deleteWindowValue(aWindow, aKey) {
  749.     if (this._windows[aWindow.__SSi].extData[aKey])
  750.       delete this._windows[aWindow.__SSi].extData[aKey];
  751.     else
  752.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  753.   },
  754.  
  755.   getTabValue: function sss_getTabValue(aTab, aKey) {
  756.     var data = aTab.__SS_extdata || {};
  757.     return data[aKey] || "";
  758.   },
  759.  
  760.   setTabValue: function sss_setTabValue(aTab, aKey, aStringValue) {
  761.     if (!aTab.__SS_extdata) {
  762.       aTab.__SS_extdata = {};
  763.     }
  764.     aTab.__SS_extdata[aKey] = aStringValue;
  765.     this.saveStateDelayed(aTab.ownerDocument.defaultView);
  766.   },
  767.  
  768.   deleteTabValue: function sss_deleteTabValue(aTab, aKey) {
  769.     if (aTab.__SS_extdata[aKey])
  770.       delete aTab.__SS_extdata[aKey];
  771.     else
  772.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  773.   },
  774.  
  775.  
  776.   persistTabAttribute: function sss_persistTabAttribute(aName) {
  777.     this.xulAttributes.push(aName);
  778.     this.saveStateDelayed();
  779.   },
  780.  
  781. /* ........ Saving Functionality .............. */
  782.  
  783.   /**
  784.    * Store all session data for a window
  785.    * @param aWindow
  786.    *        Window reference
  787.    */
  788.   _saveWindowHistory: function sss_saveWindowHistory(aWindow) {
  789.     var tabbrowser = aWindow.getBrowser();
  790.     var browsers = tabbrowser.browsers;
  791.     var tabs = this._windows[aWindow.__SSi].tabs = [];
  792.     this._windows[aWindow.__SSi].selected = 0;
  793.     
  794.     for (var i = 0; i < browsers.length; i++) {
  795.       var tabData = { entries: [], index: 0 };
  796.       
  797.       var browser = browsers[i];
  798.       if (!browser || !browser.currentURI) {
  799.         // can happen when calling this function right after .addTab()
  800.         tabs.push(tabData);
  801.         continue;
  802.       }
  803.       else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tab) {
  804.         // use the data to be restored when the tab hasn't been completely loaded
  805.         tabs.push(browser.parentNode.__SS_data);
  806.         continue;
  807.       }
  808.  
  809.       var history = null;
  810.       try {
  811.         history = browser.sessionHistory;
  812.       }
  813.       catch (ex) { } // this could happen if we catch a tab during (de)initialization
  814.       
  815.       if (history && browser.parentNode.__SS_data && browser.parentNode.__SS_data.entries[history.index]) {
  816.         tabData = browser.parentNode.__SS_data;
  817.         tabData.index = history.index + 1;
  818.       }
  819.       else if (history && history.count > 0) {
  820.         for (var j = 0; j < history.count; j++) {
  821.           tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false)));
  822.         }
  823.         tabData.index = history.index + 1;
  824.         
  825.         browser.parentNode.__SS_data = tabData;
  826.       }
  827.       else {
  828.         tabData.entries[0] = { url: browser.currentURI.spec };
  829.         tabData.index = 1;
  830.       }
  831.  
  832.       tabData.zoom = browser.markupDocumentViewer.textZoom;
  833.       
  834.       var disallow = [];
  835.       for (let i = 0; i < CAPABILITIES.length; i++) {
  836.         if (!browser.docShell["allow" + CAPABILITIES[i]])
  837.           disallow.push(CAPABILITIES[i]); 
  838.       }
  839.       if (disallow.length != 0)
  840.         tabData.disallow = disallow.join(",");
  841.       
  842.       if (this.xulAttributes.length != 0) {
  843.         var xulattr = Array.filter(tabbrowser.mTabs[i].attributes, function(aAttr) {
  844.           return (this.xulAttributes.indexOf(aAttr.name) > -1);
  845.         }, this).map(function(aAttr) {
  846.           return aAttr.name + "=" + encodeURI(aAttr.value);
  847.         });
  848.         tabData.xultab = xulattr.join(" ");
  849.       }
  850.       
  851.       if (tabbrowser.mTabs[i].__SS_extdata)
  852.         tabData.extData = tabbrowser.mTabs[i].__SS_extdata;
  853.       
  854.       tabs.push(tabData);
  855.       
  856.       if (browser == tabbrowser.selectedBrowser) {
  857.         this._windows[aWindow.__SSi].selected = i + 1;
  858.       }
  859.     }
  860.   },
  861.  
  862.   /**
  863.    * Get an object that is a serialized representation of a History entry
  864.    * Used for data storage
  865.    * @param aEntry
  866.    *        nsISHEntry instance
  867.    * @returns object
  868.    */
  869.   _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry) {
  870.     var entry = { url: aEntry.URI.spec };
  871.     
  872.     if (aEntry.title && aEntry.title != entry.url) {
  873.       entry.title = aEntry.title;
  874.     }
  875.     if (aEntry.isSubFrame) {
  876.       entry.subframe = true;
  877.     }
  878.     if (!(aEntry instanceof Ci.nsISHEntry)) {
  879.       return entry;
  880.     }
  881.     
  882.     var cacheKey = aEntry.cacheKey;
  883.     if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
  884.         cacheKey.data != 0) {
  885.       // XXXbz would be better to have cache keys implement
  886.       // nsISerializable or something.
  887.       entry.cacheKey = cacheKey.data;
  888.     }
  889.     entry.ID = aEntry.ID;
  890.     
  891.     if (aEntry.contentType)
  892.       entry.contentType = aEntry.contentType;
  893.     
  894.     var x = {}, y = {};
  895.     aEntry.getScrollPosition(x, y);
  896.     if (x.value != 0 || y.value != 0)
  897.       entry.scroll = x.value + "," + y.value;
  898.     
  899.     try {
  900.       var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
  901.       if (prefPostdata && aEntry.postData && this._checkPrivacyLevel(aEntry.URI.schemeIs("https"))) {
  902.         aEntry.postData.QueryInterface(Ci.nsISeekableStream).
  903.                         seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
  904.         var stream = Cc["@mozilla.org/binaryinputstream;1"].
  905.                      createInstance(Ci.nsIBinaryInputStream);
  906.         stream.setInputStream(aEntry.postData);
  907.         var postBytes = stream.readByteArray(stream.available());
  908.         var postdata = String.fromCharCode.apply(null, postBytes);
  909.         if (prefPostdata == -1 ||
  910.             postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
  911.               prefPostdata) {
  912.           // We can stop doing base64 encoding once our serialization into JSON
  913.           // is guaranteed to handle all chars in strings, including embedded
  914.           // nulls.
  915.           entry.postdata_b64 = btoa(postdata);
  916.         }
  917.       }
  918.     }
  919.     catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
  920.  
  921.     if (aEntry.owner) {
  922.       // Not catching anything specific here, just possible errors
  923.       // from writeCompoundObject and the like.
  924.       try {
  925.         var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
  926.                            createInstance(Ci.nsIObjectOutputStream);
  927.         var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
  928.         pipe.init(false, false, 0, 0xffffffff, null);
  929.         binaryStream.setOutputStream(pipe.outputStream);
  930.         binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
  931.         binaryStream.close();
  932.  
  933.         // Now we want to read the data from the pipe's input end and encode it.
  934.         var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
  935.                                createInstance(Ci.nsIBinaryInputStream);
  936.         scriptableStream.setInputStream(pipe.inputStream);
  937.         var ownerBytes =
  938.           scriptableStream.readByteArray(scriptableStream.available());
  939.         // We can stop doing base64 encoding once our serialization into JSON
  940.         // is guaranteed to handle all chars in strings, including embedded
  941.         // nulls.
  942.         entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
  943.       }
  944.       catch (ex) { debug(ex); }
  945.     }
  946.     
  947.     if (!(aEntry instanceof Ci.nsISHContainer)) {
  948.       return entry;
  949.     }
  950.     
  951.     if (aEntry.childCount > 0) {
  952.       entry.children = [];
  953.       for (var i = 0; i < aEntry.childCount; i++) {
  954.         var child = aEntry.GetChildAt(i);
  955.         if (child) {
  956.           entry.children.push(this._serializeHistoryEntry(child));
  957.         }
  958.         else { // to maintain the correct frame order, insert a dummy entry 
  959.           entry.children.push({ url: "about:blank" });
  960.         }
  961.       }
  962.     }
  963.     
  964.     return entry;
  965.   },
  966.  
  967.   /**
  968.    * Updates the current document's cache of user entered text data
  969.    * @param aPanel
  970.    *        TabPanel reference
  971.    * @param aTextarea
  972.    *        HTML content element
  973.    * @returns bool
  974.    */
  975.   _saveTextData: function sss_saveTextData(aPanel, aTextarea) {
  976.     var id = aTextarea.id ? "#" + aTextarea.id :
  977.                             aTextarea.name;
  978.     if (!id
  979.       || !(aTextarea instanceof Ci.nsIDOMHTMLTextAreaElement 
  980.       || aTextarea instanceof Ci.nsIDOMHTMLInputElement)) {
  981.       return false; // nothing to save
  982.     }
  983.     
  984.     if (!aPanel.__SS_text) {
  985.       aPanel.__SS_text = [];
  986.       aPanel.__SS_text._refs = [];
  987.     }
  988.     
  989.     // get the index of the reference to the text element
  990.     var ix = aPanel.__SS_text._refs.indexOf(aTextarea);
  991.     if (ix == -1) {
  992.       // we haven't registered this text element yet - do so now
  993.       aPanel.__SS_text._refs.push(aTextarea);
  994.       ix = aPanel.__SS_text.length;
  995.     }
  996.     else if (!aPanel.__SS_text[ix].cache) {
  997.       // we've already marked this text element for saving (the cache is
  998.       // added during save operations and would have to be updated here)
  999.       return false;
  1000.     }
  1001.     
  1002.     // determine the frame we're in and encode it into the textarea's ID
  1003.     var content = aTextarea.ownerDocument.defaultView;
  1004.     while (content != content.top) {
  1005.       var frames = content.parent.frames;
  1006.       for (var i = 0; i < frames.length && frames[i] != content; i++);
  1007.       id = i + "|" + id;
  1008.       content = content.parent;
  1009.     }
  1010.     
  1011.     // mark this element for saving
  1012.     aPanel.__SS_text[ix] = { id: id, element: aTextarea };
  1013.     
  1014.     return true;
  1015.   },
  1016.  
  1017.   /**
  1018.    * go through all frames and store the current scroll positions
  1019.    * and innerHTML content of WYSIWYG editors
  1020.    * @param aWindow
  1021.    *        Window reference
  1022.    */
  1023.   _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
  1024.     var _this = this;
  1025.     function updateRecursively(aContent, aData) {
  1026.       for (var i = 0; i < aContent.frames.length; i++) {
  1027.         if (aData.children && aData.children[i]) {
  1028.           updateRecursively(aContent.frames[i], aData.children[i]);
  1029.         }
  1030.       }
  1031.       // designMode is undefined e.g. for XUL documents (as about:config)
  1032.       var isHTTPS = _this._getURIFromString((aContent.parent || aContent).
  1033.                                         document.location.href).schemeIs("https");
  1034.       if ((aContent.document.designMode || "") == "on" && _this._checkPrivacyLevel(isHTTPS)) {
  1035.         if (aData.innerHTML == undefined) {
  1036.           // we get no "input" events from iframes - listen for keypress here
  1037.           aContent.addEventListener("keypress", function(aEvent) { _this.saveStateDelayed(aWindow, 3000); }, true);
  1038.         }
  1039.         aData.innerHTML = aContent.document.body.innerHTML;
  1040.       }
  1041.       aData.scroll = aContent.scrollX + "," + aContent.scrollY;
  1042.     }
  1043.     
  1044.     Array.forEach(aWindow.getBrowser().browsers, function(aBrowser, aIx) {
  1045.       try {
  1046.         var tabData = this._windows[aWindow.__SSi].tabs[aIx];
  1047.         if (tabData.entries.length == 0)
  1048.           return; // ignore incompletely initialized tabs
  1049.         
  1050.         var text = [];
  1051.         if (aBrowser.parentNode.__SS_text && this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https"))) {
  1052.           for (var ix = aBrowser.parentNode.__SS_text.length - 1; ix >= 0; ix--) {
  1053.             var data = aBrowser.parentNode.__SS_text[ix];
  1054.             if (!data.cache) {
  1055.               // update the text element's value before adding it to the data structure
  1056.               data.cache = encodeURI(data.element.value);
  1057.             }
  1058.             text.push(data.id + "=" + data.cache);
  1059.           }
  1060.         }
  1061.         if (aBrowser.currentURI.spec == "about:config") {
  1062.           text = ["#textbox=" + encodeURI(aBrowser.contentDocument.getElementById("textbox").wrappedJSObject.value)];
  1063.         }
  1064.         if (text.length != 0)
  1065.           tabData.text = text.join(" ");
  1066.         
  1067.         updateRecursively(aBrowser.contentWindow, tabData.entries[tabData.index - 1]);
  1068.       }
  1069.       catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
  1070.     }, this);
  1071.   },
  1072.  
  1073.   /**
  1074.    * store all hosts for a URL
  1075.    * @param aWindow
  1076.    *        Window reference
  1077.    */
  1078.   _updateCookieHosts: function sss_updateCookieHosts(aWindow) {
  1079.     var hosts = this._windows[aWindow.__SSi]._hosts = {};
  1080.     
  1081.     // get all possible subdomain levels for a given URL
  1082.     var _this = this;
  1083.     function extractHosts(aEntry) {
  1084.       if (/^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.test(aEntry.url) &&
  1085.         !hosts[RegExp.$1] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
  1086.         var host = RegExp.$1;
  1087.         var ix;
  1088.         for (ix = host.indexOf(".") + 1; ix; ix = host.indexOf(".", ix) + 1) {
  1089.           hosts[host.substr(ix)] = true;
  1090.         }
  1091.         hosts[host] = true;
  1092.       }
  1093.       else if (/^file:\/\/([^\/]*)/.test(aEntry.url)) {
  1094.         hosts[RegExp.$1] = true;
  1095.       }
  1096.       if (aEntry.children) {
  1097.         aEntry.children.forEach(extractHosts);
  1098.       }
  1099.     }
  1100.     
  1101.     this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
  1102.   },
  1103.  
  1104.   /**
  1105.    * Serialize cookie data
  1106.    * @param aWindows
  1107.    *        array of Window references
  1108.    */
  1109.   _updateCookies: function sss_updateCookies(aWindows) {
  1110.     var cookiesEnum = Cc["@mozilla.org/cookiemanager;1"].
  1111.                       getService(Ci.nsICookieManager).enumerator;
  1112.     // collect the cookies per window
  1113.     for (var i = 0; i < aWindows.length; i++)
  1114.       aWindows[i].cookies = [];
  1115.     
  1116.     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1117.     var MAX_EXPIRY = Math.pow(2, 62);
  1118.     while (cookiesEnum.hasMoreElements()) {
  1119.       var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
  1120.       if (cookie.isSession && this._checkPrivacyLevel(cookie.isSecure)) {
  1121.         var jscookie = null;
  1122.         aWindows.forEach(function(aWindow) {
  1123.           if (aWindow._hosts && aWindow._hosts[cookie.rawHost]) {
  1124.             // serialize the cookie when it's first needed
  1125.             if (!jscookie) {
  1126.               jscookie = { host: cookie.host, value: cookie.value };
  1127.               // only add attributes with non-default values (saving a few bits)
  1128.               if (cookie.path) jscookie.path = cookie.path;
  1129.               if (cookie.name) jscookie.name = cookie.name;
  1130.               if (cookie.isSecure) jscookie.secure = true;
  1131.               if (cookie.isHttpOnly) jscookie.httponly = true;
  1132.               if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
  1133.             }
  1134.             aWindow.cookies.push(jscookie);
  1135.           }
  1136.         });
  1137.       }
  1138.     }
  1139.     
  1140.     // don't include empty cookie sections
  1141.     for (i = 0; i < aWindows.length; i++)
  1142.       if (aWindows[i].cookies.length == 0)
  1143.         delete aWindows[i].cookies;
  1144.   },
  1145.  
  1146.   /**
  1147.    * Store window dimensions, visibility, sidebar
  1148.    * @param aWindow
  1149.    *        Window reference
  1150.    */
  1151.   _updateWindowFeatures: function sss_updateWindowFeatures(aWindow) {
  1152.     var winData = this._windows[aWindow.__SSi];
  1153.     
  1154.     WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  1155.       var value = this._getWindowDimension(aWindow, aAttr);
  1156.       switch (aAttr) {
  1157.         case "screenX":
  1158.         case "screenY":
  1159.           if (value != 0)
  1160.             winData[aAttr] = value;
  1161.           break;
  1162.         default:
  1163.           winData[aAttr] = value;
  1164.       }
  1165.     }, this);
  1166.     
  1167.     var hidden = [];
  1168.     WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
  1169.       if (aWindow[aItem] && !aWindow[aItem].visible)
  1170.         hidden.push(aItem);
  1171.     });
  1172.     if (hidden.length != 0)
  1173.       winData.hidden = hidden.join(",");
  1174.  
  1175.     var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
  1176.     if (sidebar)
  1177.       winData.sidebar = sidebar;
  1178.   },
  1179.  
  1180.   /**
  1181.    * serialize session data as Ini-formatted string
  1182.    * @returns string
  1183.    */
  1184.   _getCurrentState: function sss_getCurrentState() {
  1185.     var activeWindow = this._getMostRecentBrowserWindow();
  1186.     
  1187.     if (this._loadState == STATE_RUNNING) {
  1188.       // update the data for all windows with activities since the last save operation
  1189.       this._forEachBrowserWindow(function(aWindow) {
  1190.         if (this._dirty || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
  1191.           this._collectWindowData(aWindow);
  1192.         }
  1193.         else { // always update the window features (whose change alone never triggers a save operation)
  1194.           this._updateWindowFeatures(aWindow);
  1195.         }
  1196.       }, this);
  1197.       this._dirtyWindows = [];
  1198.       this._dirty = false;
  1199.     }
  1200.     
  1201.     // collect the data for all windows
  1202.     var total = [], windows = [];
  1203.     var ix;
  1204.     for (ix in this._windows) {
  1205.       total.push(this._windows[ix]);
  1206.       windows.push(ix);
  1207.     }
  1208.     this._updateCookies(total);
  1209.     
  1210.     // if no browser window remains open, return the state of the last closed window
  1211.     if (total.length == 0 && this._lastWindowClosed) {
  1212.       total.push(this._lastWindowClosed);
  1213.     }
  1214.     if (activeWindow) {
  1215.       this.activeWindowSSiCache = activeWindow.__SSi || "";
  1216.     }
  1217.     ix = this.activeWindowSSiCache ? windows.indexOf(this.activeWindowSSiCache) : -1;
  1218.  
  1219.     return { windows: total, selectedWindow: ix + 1 };
  1220.   },
  1221.  
  1222.   /**
  1223.    * serialize session data for a window 
  1224.    * @param aWindow
  1225.    *        Window reference
  1226.    * @returns string
  1227.    */
  1228.   _getWindowState: function sss_getWindowState(aWindow) {
  1229.     if (this._loadState == STATE_RUNNING) {
  1230.       this._collectWindowData(aWindow);
  1231.     }
  1232.     
  1233.     var total = [this._windows[aWindow.__SSi]];
  1234.     this._updateCookies(total);
  1235.     
  1236.     return { windows: total };
  1237.   },
  1238.  
  1239.   _collectWindowData: function sss_collectWindowData(aWindow) {
  1240.     // update the internal state data for this window
  1241.     this._saveWindowHistory(aWindow);
  1242.     this._updateTextAndScrollData(aWindow);
  1243.     this._updateCookieHosts(aWindow);
  1244.     this._updateWindowFeatures(aWindow);
  1245.     
  1246.     this._dirtyWindows[aWindow.__SSi] = false;
  1247.   },
  1248.  
  1249. /* ........ Restoring Functionality .............. */
  1250.  
  1251.   /**
  1252.    * restore features to a single window
  1253.    * @param aWindow
  1254.    *        Window reference
  1255.    * @param aState
  1256.    *        JS object or its eval'able source
  1257.    * @param aOverwriteTabs
  1258.    *        bool overwrite existing tabs w/ new ones
  1259.    * @param aFollowUp
  1260.    *        bool this isn't the restoration of the first window
  1261.    */
  1262.   restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
  1263.     if (!aFollowUp) {
  1264.       this.windowToFocus = aWindow;
  1265.     }
  1266.     // initialize window if necessary
  1267.     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  1268.       this.onLoad(aWindow);
  1269.  
  1270.     try {
  1271.       var root = typeof aState == "string" ? this._safeEval(aState) : aState;
  1272.       if (!root.windows[0]) {
  1273.         return; // nothing to restore
  1274.       }
  1275.     }
  1276.     catch (ex) { // invalid state object - don't restore anything 
  1277.       debug(ex);
  1278.       return;
  1279.     }
  1280.     
  1281.     var winData;
  1282.     if (!aState.selectedWindow) {
  1283.       aState.selectedWindow = 0;
  1284.     }
  1285.     // open new windows for all further window entries of a multi-window session
  1286.     // (unless they don't contain any tab data)
  1287.     for (var w = 1; w < root.windows.length; w++) {
  1288.       winData = root.windows[w];
  1289.       if (winData && winData.tabs && winData.tabs[0]) {
  1290.         var window = this._openWindowWithState({ windows: [winData] });
  1291.         if (w == aState.selectedWindow - 1) {
  1292.           this.windowToFocus = window;
  1293.         }
  1294.       }
  1295.     }
  1296.     winData = root.windows[0];
  1297.     if (!winData.tabs) {
  1298.       winData.tabs = [];
  1299.     }
  1300.     
  1301.     var tabbrowser = aWindow.getBrowser();
  1302.     var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
  1303.     var newTabCount = winData.tabs.length;
  1304.     
  1305.     for (var t = 0; t < newTabCount; t++) {
  1306.       winData.tabs[t]._tab = t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab();
  1307.       // when resuming at startup: add additionally requested pages to the end
  1308.       if (!aOverwriteTabs && root._firstTabs) {
  1309.         tabbrowser.moveTabTo(winData.tabs[t]._tab, t);
  1310.       }
  1311.     }
  1312.  
  1313.     // when overwriting tabs, remove all superflous ones
  1314.     for (t = openTabCount - 1; t >= newTabCount; t--) {
  1315.       tabbrowser.removeTab(tabbrowser.mTabs[t]);
  1316.     }
  1317.     
  1318.     if (aOverwriteTabs) {
  1319.       this.restoreWindowFeatures(aWindow, winData);
  1320.     }
  1321.     if (winData.cookies) {
  1322.       this.restoreCookies(winData.cookies);
  1323.     }
  1324.     if (winData.extData) {
  1325.       if (!this._windows[aWindow.__SSi].extData) {
  1326.         this._windows[aWindow.__SSi].extData = {}
  1327.       }
  1328.       for (var key in winData.extData) {
  1329.         this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
  1330.       }
  1331.     }
  1332.     if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) {
  1333.       this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs;
  1334.     }
  1335.     
  1336.     this.restoreHistoryPrecursor(aWindow, winData.tabs, (aOverwriteTabs ?
  1337.       (parseInt(winData.selected) || 1) : 0), 0, 0);
  1338.   },
  1339.  
  1340.   /**
  1341.    * Manage history restoration for a window
  1342.    * @param aTabs
  1343.    *        Array of tab data
  1344.    * @param aCurrentTabs
  1345.    *        Array of tab references
  1346.    * @param aSelectTab
  1347.    *        Index of selected tab
  1348.    * @param aCount
  1349.    *        Counter for number of times delaying b/c browser or history aren't ready
  1350.    */
  1351.   restoreHistoryPrecursor: function sss_restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount) {
  1352.     var tabbrowser = aWindow.getBrowser();
  1353.     
  1354.     // make sure that all browsers and their histories are available
  1355.     // - if one's not, resume this check in 100ms (repeat at most 10 times)
  1356.     for (var t = aIx; t < aTabs.length; t++) {
  1357.       try {
  1358.         if (!tabbrowser.getBrowserForTab(aTabs[t]._tab).webNavigation.sessionHistory) {
  1359.           throw new Error();
  1360.         }
  1361.       }
  1362.       catch (ex) { // in case browser or history aren't ready yet 
  1363.         if (aCount < 10) {
  1364.           var restoreHistoryFunc = function(self) {
  1365.             self.restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount + 1);
  1366.           }
  1367.           aWindow.setTimeout(restoreHistoryFunc, 100, this);
  1368.           return;
  1369.         }
  1370.       }
  1371.     }
  1372.     
  1373.     // mark the tabs as loading
  1374.     for (t = 0; t < aTabs.length; t++) {
  1375.       if (!aTabs[t].entries || !aTabs[t].entries[0])
  1376.         continue; // there won't be anything to load
  1377.       
  1378.       var tab = aTabs[t]._tab;
  1379.       var browser = tabbrowser.getBrowserForTab(tab);
  1380.       browser.stop(); // in case about:blank isn't done yet
  1381.       
  1382.       tab.setAttribute("busy", "true");
  1383.       tabbrowser.updateIcon(tab);
  1384.       tabbrowser.setTabTitleLoading(tab);
  1385.       
  1386.       // keep the data around to prevent dataloss in case
  1387.       // a tab gets closed before it's been properly restored
  1388.       browser.parentNode.__SS_data = aTabs[t];
  1389.     }
  1390.     
  1391.     // make sure to restore the selected tab first (if any)
  1392.     if (aSelectTab-- && aTabs[aSelectTab]) {
  1393.         aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
  1394.         tabbrowser.selectedTab = aTabs[0]._tab;
  1395.     }
  1396.  
  1397.     // helper hash for ensuring unique frame IDs
  1398.     var idMap = { used: {} };
  1399.     this.restoreHistory(aWindow, aTabs, idMap);
  1400.   },
  1401.  
  1402.   /**
  1403.    * Restory history for a window
  1404.    * @param aWindow
  1405.    *        Window reference
  1406.    * @param aTabs
  1407.    *        Array of tab data
  1408.    * @param aIdMap
  1409.    *        Hash for ensuring unique frame IDs
  1410.    */
  1411.   restoreHistory: function sss_restoreHistory(aWindow, aTabs, aIdMap) {
  1412.     var _this = this;
  1413.     while (aTabs.length > 0 && (!aTabs[0]._tab || !aTabs[0]._tab.parentNode)) {
  1414.       aTabs.shift(); // this tab got removed before being completely restored
  1415.     }
  1416.     if (aTabs.length == 0) {
  1417.       return; // no more tabs to restore
  1418.     }
  1419.     
  1420.     var tabData = aTabs.shift();
  1421.  
  1422.     var tab = tabData._tab;
  1423.     var browser = aWindow.getBrowser().getBrowserForTab(tab);
  1424.     var history = browser.webNavigation.sessionHistory;
  1425.     
  1426.     if (history.count > 0) {
  1427.       history.PurgeHistory(history.count);
  1428.     }
  1429.     history.QueryInterface(Ci.nsISHistoryInternal);
  1430.     
  1431.     if (!tabData.entries) {
  1432.       tabData.entries = [];
  1433.     }
  1434.     if (tabData.extData) {
  1435.       tab.__SS_extdata = tabData.extData;
  1436.     }
  1437.     
  1438.     browser.markupDocumentViewer.textZoom = parseFloat(tabData.zoom || 1);
  1439.     
  1440.     for (var i = 0; i < tabData.entries.length; i++) {
  1441.       history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
  1442.     }
  1443.     
  1444.     // make sure to reset the capabilities and attributes, in case this tab gets reused
  1445.     var disallow = (tabData.disallow)?tabData.disallow.split(","):[];
  1446.     CAPABILITIES.forEach(function(aCapability) {
  1447.       browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
  1448.     });
  1449.     Array.filter(tab.attributes, function(aAttr) {
  1450.       return (_this.xulAttributes.indexOf(aAttr.name) > -1);
  1451.     }).forEach(tab.removeAttribute, tab);
  1452.     if (tabData.xultab) {
  1453.       tabData.xultab.split(" ").forEach(function(aAttr) {
  1454.         if (/^([^\s=]+)=(.*)/.test(aAttr)) {
  1455.           tab.setAttribute(RegExp.$1, decodeURI(RegExp.$2));
  1456.         }
  1457.       });
  1458.     }
  1459.     
  1460.     // notify the tabbrowser that the tab chrome has been restored
  1461.     var event = aWindow.document.createEvent("Events");
  1462.     event.initEvent("SSTabRestoring", true, false);
  1463.     tab.dispatchEvent(event);
  1464.     
  1465.     var activeIndex = (tabData.index || tabData.entries.length) - 1;
  1466.     try {
  1467.       browser.webNavigation.gotoIndex(activeIndex);
  1468.     }
  1469.     catch (ex) { } // ignore an invalid tabData.index
  1470.     
  1471.     // restore those aspects of the currently active documents
  1472.     // which are not preserved in the plain history entries
  1473.     // (mainly scroll state and text data)
  1474.     browser.__SS_restore_data = tabData.entries[activeIndex] || {};
  1475.     browser.__SS_restore_text = tabData.text || "";
  1476.     browser.__SS_restore_tab = tab;
  1477.     browser.__SS_restore = this.restoreDocument_proxy;
  1478.     browser.addEventListener("load", browser.__SS_restore, true);
  1479.     
  1480.     aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aIdMap); }, 0);
  1481.   },
  1482.  
  1483.   /**
  1484.    * expands serialized history data into a session-history-entry instance
  1485.    * @param aEntry
  1486.    *        Object containing serialized history data for a URL
  1487.    * @param aIdMap
  1488.    *        Hash for ensuring unique frame IDs
  1489.    * @returns nsISHEntry
  1490.    */
  1491.   _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
  1492.     var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
  1493.                   createInstance(Ci.nsISHEntry);
  1494.     
  1495.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  1496.                     getService(Ci.nsIIOService);
  1497.     shEntry.setURI(ioService.newURI(aEntry.url, null, null));
  1498.     shEntry.setTitle(aEntry.title || aEntry.url);
  1499.     if (aEntry.subframe)
  1500.       shEntry.setIsSubFrame(aEntry.subframe || false);
  1501.     shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
  1502.     if (aEntry.contentType)
  1503.       shEntry.contentType = aEntry.contentType;
  1504.     
  1505.     if (aEntry.cacheKey) {
  1506.       var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
  1507.                      createInstance(Ci.nsISupportsPRUint32);
  1508.       cacheKey.data = aEntry.cacheKey;
  1509.       shEntry.cacheKey = cacheKey;
  1510.     }
  1511.  
  1512.     if (aEntry.ID) {
  1513.       // get a new unique ID for this frame (since the one from the last
  1514.       // start might already be in use)
  1515.       var id = aIdMap[aEntry.ID] || 0;
  1516.       if (!id) {
  1517.         for (id = Date.now(); aIdMap.used[id]; id++);
  1518.         aIdMap[aEntry.ID] = id;
  1519.         aIdMap.used[id] = true;
  1520.       }
  1521.       shEntry.ID = id;
  1522.     }
  1523.     
  1524.     if (aEntry.scroll) {
  1525.       var scrollPos = (aEntry.scroll || "0,0").split(",");
  1526.       scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
  1527.       shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
  1528.     }
  1529.  
  1530.     var postdata;
  1531.     if (aEntry.postdata_b64) {  // Firefox 3
  1532.       postdata = atob(aEntry.postdata_b64);
  1533.     } else if (aEntry.postdata) { // Firefox 2
  1534.       postdata = aEntry.postdata;
  1535.     }
  1536.  
  1537.     if (postdata) {
  1538.       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
  1539.                    createInstance(Ci.nsIStringInputStream);
  1540.       stream.setData(postdata, postdata.length);
  1541.       shEntry.postData = stream;
  1542.     }
  1543.  
  1544.     if (aEntry.owner_b64) {  // Firefox 3
  1545.       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
  1546.                        createInstance(Ci.nsIStringInputStream);
  1547.       var binaryData = atob(aEntry.owner_b64);
  1548.       ownerInput.setData(binaryData, binaryData.length);
  1549.       var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
  1550.                          createInstance(Ci.nsIObjectInputStream);
  1551.       binaryStream.setInputStream(ownerInput);
  1552.       try { // Catch possible deserialization exceptions
  1553.         shEntry.owner = binaryStream.readObject(true);
  1554.       } catch (ex) { debug(ex); }
  1555.     } else if (aEntry.ownerURI) { // Firefox 2
  1556.       var uriObj = ioService.newURI(aEntry.ownerURI, null, null);
  1557.       shEntry.owner = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1558.                       getService(Ci.nsIScriptSecurityManager).
  1559.                       getCodebasePrincipal(uriObj);
  1560.     }
  1561.     
  1562.     if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
  1563.       for (var i = 0; i < aEntry.children.length; i++) {
  1564.         shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
  1565.       }
  1566.     }
  1567.     
  1568.     return shEntry;
  1569.   },
  1570.  
  1571.   /**
  1572.    * Restore properties to a loaded document
  1573.    */
  1574.   restoreDocument_proxy: function sss_restoreDocument_proxy(aEvent) {
  1575.     // wait for the top frame to be loaded completely
  1576.     if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
  1577.       return;
  1578.     }
  1579.     
  1580.     var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : [];
  1581.     function restoreTextData(aContent, aPrefix) {
  1582.       textArray.forEach(function(aEntry) {
  1583.         if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) && (!RegExp.$1 || RegExp.$1 == aPrefix)) {
  1584.           var document = aContent.document;
  1585.           var node = RegExp.$2 ? document.getElementById(RegExp.$3) : document.getElementsByName(RegExp.$3)[0] || null;
  1586.           if (node && "value" in node) {
  1587.             node.value = decodeURI(RegExp.$4);
  1588.             
  1589.             var event = document.createEvent("UIEvents");
  1590.             event.initUIEvent("input", true, true, aContent, 0);
  1591.             node.dispatchEvent(event);
  1592.           }
  1593.         }
  1594.       });
  1595.     }
  1596.     
  1597.     function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
  1598.       restoreTextData(aContent, aPrefix);
  1599.       if (aData.innerHTML) {
  1600.         aContent.setTimeout(function(aHTML) { if (this.document.designMode == "on") { this.document.body.innerHTML = aHTML; } }, 0, aData.innerHTML);
  1601.       }
  1602.       if (aData.scroll && /(\d+),(\d+)/.test(aData.scroll)) {
  1603.         aContent.scrollTo(RegExp.$1, RegExp.$2);
  1604.       }
  1605.       for (var i = 0; i < aContent.frames.length; i++) {
  1606.         if (aData.children && aData.children[i]) {
  1607.           restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], i + "|" + aPrefix);
  1608.         }
  1609.       }
  1610.     }
  1611.     
  1612.     var content = aEvent.originalTarget.defaultView;
  1613.     if (this.currentURI.spec == "about:config") {
  1614.       // unwrap the document for about:config because otherwise the properties
  1615.       // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
  1616.       content = content.wrappedJSObject;
  1617.     }
  1618.     restoreTextDataAndScrolling(content, this.__SS_restore_data, "");
  1619.     
  1620.     // notify the tabbrowser that this document has been completely restored
  1621.     var event = this.ownerDocument.createEvent("Events");
  1622.     event.initEvent("SSTabRestored", true, false);
  1623.     this.__SS_restore_tab.dispatchEvent(event);
  1624.     
  1625.     this.removeEventListener("load", this.__SS_restore, true);
  1626.     delete this.__SS_restore_data;
  1627.     delete this.__SS_restore_text;
  1628.     delete this.__SS_restore_tab;
  1629.     delete this.__SS_restore;
  1630.   },
  1631.  
  1632.   /**
  1633.    * Restore visibility and dimension features to a window
  1634.    * @param aWindow
  1635.    *        Window reference
  1636.    * @param aWinData
  1637.    *        Object containing session data for the window
  1638.    */
  1639.   restoreWindowFeatures: function sss_restoreWindowFeatures(aWindow, aWinData) {
  1640.     var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
  1641.     WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
  1642.       aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
  1643.     });
  1644.     
  1645.     var _this = this;
  1646.     aWindow.setTimeout(function() {
  1647.       _this.restoreDimensions.apply(_this, [aWindow, aWinData.width || 0, 
  1648.         aWinData.height || 0, "screenX" in aWinData ? aWinData.screenX : NaN,
  1649.         "screenY" in aWinData ? aWinData.screenY : NaN,
  1650.         aWinData.sizemode || "", aWinData.sidebar || ""]);
  1651.     }, 0);
  1652.   },
  1653.  
  1654.   /**
  1655.    * Restore a window's dimensions
  1656.    * @param aWidth
  1657.    *        Window width
  1658.    * @param aHeight
  1659.    *        Window height
  1660.    * @param aLeft
  1661.    *        Window left
  1662.    * @param aTop
  1663.    *        Window top
  1664.    * @param aSizeMode
  1665.    *        Window size mode (eg: maximized)
  1666.    * @param aSidebar
  1667.    *        Sidebar command
  1668.    */
  1669.   restoreDimensions: function sss_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
  1670.     var win = aWindow;
  1671.     var _this = this;
  1672.     function win_(aName) { return _this._getWindowDimension(win, aName); }
  1673.     
  1674.     // only modify those aspects which aren't correct yet
  1675.     if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
  1676.       aWindow.resizeTo(aWidth, aHeight);
  1677.     }
  1678.     if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
  1679.       aWindow.moveTo(aLeft, aTop);
  1680.     }
  1681.     if (aSizeMode == "maximized" && win_("sizemode") != "maximized") {
  1682.       aWindow.maximize();
  1683.     }
  1684.     else if (aSizeMode && aSizeMode != "maximized" && win_("sizemode") != "normal") {
  1685.       aWindow.restore();
  1686.     }
  1687.     var sidebar = aWindow.document.getElementById("sidebar-box");
  1688.     if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
  1689.       aWindow.toggleSidebar(aSidebar);
  1690.     }
  1691.     // since resizing/moving a window brings it to the foreground,
  1692.     // we might want to re-focus the last focused window
  1693.     if (this.windowToFocus) {
  1694.       this.windowToFocus.focus();
  1695.     }
  1696.   },
  1697.  
  1698.   /**
  1699.    * Restores cookies (accepting both Firefox 2.0 and current format)
  1700.    * @param aCookies
  1701.    *        Array of cookie objects
  1702.    */
  1703.   restoreCookies: function sss_restoreCookies(aCookies) {
  1704.     if (aCookies.count && aCookies.domain1) {
  1705.       // convert to the new cookie serialization format
  1706.       var converted = [];
  1707.       for (var i = 1; i <= aCookies.count; i++) {
  1708.         // for simplicity we only accept the format we produced ourselves
  1709.         var parsed = aCookies["value" + i].match(/^([^=;]+)=([^;]*);(?:domain=[^;]+;)?(?:path=([^;]*);)?(secure;)?(httponly;)?/);
  1710.         if (parsed && /^https?:\/\/([^\/]+)/.test(aCookies["domain" + i]))
  1711.           converted.push({
  1712.             host: RegExp.$1, path: parsed[3], name: parsed[1], value: parsed[2],
  1713.             secure: parsed[4], httponly: parsed[5]
  1714.           });
  1715.       }
  1716.       aCookies = converted;
  1717.     }
  1718.     
  1719.     var cookieManager = Cc["@mozilla.org/cookiemanager;1"].
  1720.                         getService(Ci.nsICookieManager2);
  1721.     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1722.     var MAX_EXPIRY = Math.pow(2, 62);
  1723.     for (i = 0; i < aCookies.length; i++) {
  1724.       var cookie = aCookies[i];
  1725.       try {
  1726.         cookieManager.add(cookie.host, cookie.path || "", cookie.name || "", cookie.value, !!cookie.secure, !!cookie.httponly, true, "expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
  1727.       }
  1728.       catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
  1729.     }
  1730.   },
  1731.  
  1732.   /**
  1733.    * Restart incomplete downloads
  1734.    */
  1735.   retryDownloads: function sss_retryDownloads() {
  1736.     // The download manager cleans up after itself when it is created.
  1737.     var dlManager = Cc["@mozilla.org/download-manager;1"].
  1738.                     getService(Ci.nsIDownloadManager);
  1739.   },
  1740.  
  1741. /* ........ Disk Access .............. */
  1742.  
  1743.   /**
  1744.    * save state delayed by N ms
  1745.    * marks window as dirty (i.e. data update can't be skipped)
  1746.    * @param aWindow
  1747.    *        Window reference
  1748.    * @param aDelay
  1749.    *        Milliseconds to delay
  1750.    */
  1751.   saveStateDelayed: function sss_saveStateDelayed(aWindow, aDelay) {
  1752.     if (aWindow) {
  1753.       this._dirtyWindows[aWindow.__SSi] = true;
  1754.     }
  1755.  
  1756.     if (!this._saveTimer && this._resume_from_crash) {
  1757.       // interval until the next disk operation is allowed
  1758.       var minimalDelay = this._lastSaveTime + this._interval - Date.now();
  1759.       
  1760.       // if we have to wait, set a timer, otherwise saveState directly
  1761.       aDelay = Math.max(minimalDelay, aDelay || 2000);
  1762.       if (aDelay > 0) {
  1763.         this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1764.         this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
  1765.       }
  1766.       else {
  1767.         this.saveState();
  1768.       }
  1769.     }
  1770.   },
  1771.  
  1772.   /**
  1773.    * save state to disk
  1774.    * @param aUpdateAll
  1775.    *        Bool update all windows 
  1776.    */
  1777.   saveState: function sss_saveState(aUpdateAll) {
  1778.     // if crash recovery is disabled, only save session resuming information
  1779.     if (!this._resume_from_crash && this._loadState == STATE_RUNNING)
  1780.       return;
  1781.     
  1782.     this._dirty = aUpdateAll;
  1783.     var oState = this._getCurrentState();
  1784.     oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) };
  1785.     this._writeFile(this._sessionFile, oState.toSource());
  1786.     this._lastSaveTime = Date.now();
  1787.   },
  1788.  
  1789.   /**
  1790.    * delete session datafile and backup
  1791.    */
  1792.   _clearDisk: function sss_clearDisk() {
  1793.     if (this._sessionFile.exists()) {
  1794.       try {
  1795.         this._sessionFile.remove(false);
  1796.       }
  1797.       catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
  1798.     }
  1799.     if (this._sessionFileBackup.exists()) {
  1800.       try {
  1801.         this._sessionFileBackup.remove(false);
  1802.       }
  1803.       catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
  1804.     }
  1805.   },
  1806.  
  1807. /* ........ Auxiliary Functions .............. */
  1808.  
  1809.   /**
  1810.    * call a callback for all currently opened browser windows
  1811.    * (might miss the most recent one)
  1812.    * @param aFunc
  1813.    *        Callback each window is passed to
  1814.    */
  1815.   _forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) {
  1816.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1817.                          getService(Ci.nsIWindowMediator);
  1818.     var windowsEnum = windowMediator.getEnumerator("navigator:browser");
  1819.     
  1820.     while (windowsEnum.hasMoreElements()) {
  1821.       var window = windowsEnum.getNext();
  1822.       if (window.__SSi) {
  1823.         aFunc.call(this, window);
  1824.       }
  1825.     }
  1826.   },
  1827.  
  1828.   /**
  1829.    * Returns most recent window
  1830.    * @returns Window reference
  1831.    */
  1832.   _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() {
  1833.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1834.                          getService(Ci.nsIWindowMediator);
  1835.     return windowMediator.getMostRecentWindow("navigator:browser");
  1836.   },
  1837.  
  1838.   /**
  1839.    * open a new browser window for a given session state
  1840.    * called when restoring a multi-window session
  1841.    * @param aState
  1842.    *        Object containing session data
  1843.    */
  1844.   _openWindowWithState: function sss_openWindowWithState(aState) {
  1845.     var argString = Cc["@mozilla.org/supports-string;1"].
  1846.                     createInstance(Ci.nsISupportsString);
  1847.     argString.data = "";
  1848.  
  1849.     //XXXzeniko shouldn't it be possible to set the window's dimensions here (as feature)?
  1850.     var window = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  1851.                  getService(Ci.nsIWindowWatcher).
  1852.                  openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank",
  1853.                             "chrome,dialog=no,all", argString);
  1854.     
  1855.     window.__SS_state = aState;
  1856.     var _this = this;
  1857.     window.addEventListener("load", function(aEvent) {
  1858.       aEvent.currentTarget.removeEventListener("load", arguments.callee, true);
  1859.       _this.restoreWindow(aEvent.currentTarget, aEvent.currentTarget.__SS_state, true, true);
  1860.       delete aEvent.currentTarget.__SS_state;
  1861.     }, true);
  1862.     
  1863.     return window;
  1864.   },
  1865.  
  1866.   /**
  1867.    * Whether or not to resume session, if not recovering from a crash.
  1868.    * @returns bool
  1869.    */
  1870.   _doResumeSession: function sss_doResumeSession() {
  1871.     return this._prefBranch.getIntPref("startup.page") == 3 ||
  1872.       this._prefBranch.getBoolPref("sessionstore.resume_session_once");
  1873.   },
  1874.  
  1875.   /**
  1876.    * whether the user wants to load any other page at startup
  1877.    * (except the homepage) - needed for determining whether to overwrite the current tabs
  1878.    * C.f.: nsBrowserContentHandler's defaultArgs implementation.
  1879.    * @returns bool
  1880.    */
  1881.   _isCmdLineEmpty: function sss_isCmdLineEmpty(aWindow) {
  1882.     var defaultArgs = Cc["@mozilla.org/browser/clh;1"].
  1883.                       getService(Ci.nsIBrowserHandler).defaultArgs;
  1884.     if (aWindow.arguments && aWindow.arguments[0] &&
  1885.         aWindow.arguments[0] == defaultArgs)
  1886.       aWindow.arguments[0] = null;
  1887.  
  1888.     return !aWindow.arguments || !aWindow.arguments[0];
  1889.   },
  1890.  
  1891.   /**
  1892.    * don't save sensitive data if the user doesn't want to
  1893.    * (distinguishes between encrypted and non-encrypted sites)
  1894.    * @param aIsHTTPS
  1895.    *        Bool is encrypted
  1896.    * @returns bool
  1897.    */
  1898.   _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
  1899.     return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
  1900.   },
  1901.  
  1902.   /**
  1903.    * on popup windows, the XULWindow's attributes seem not to be set correctly
  1904.    * we use thus JSDOMWindow attributes for sizemode and normal window attributes
  1905.    * (and hope for reasonable values when maximized/minimized - since then
  1906.    * outerWidth/outerHeight aren't the dimensions of the restored window)
  1907.    * @param aWindow
  1908.    *        Window reference
  1909.    * @param aAttribute
  1910.    *        String sizemode | width | height | other window attribute
  1911.    * @returns string
  1912.    */
  1913.   _getWindowDimension: function sss_getWindowDimension(aWindow, aAttribute) {
  1914.     if (aAttribute == "sizemode") {
  1915.       switch (aWindow.windowState) {
  1916.       case aWindow.STATE_MAXIMIZED:
  1917.         return "maximized";
  1918.       case aWindow.STATE_MINIMIZED:
  1919.         return "minimized";
  1920.       default:
  1921.         return "normal";
  1922.       }
  1923.     }
  1924.     
  1925.     var dimension;
  1926.     switch (aAttribute) {
  1927.     case "width":
  1928.       dimension = aWindow.outerWidth;
  1929.       break;
  1930.     case "height":
  1931.       dimension = aWindow.outerHeight;
  1932.       break;
  1933.     default:
  1934.       dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
  1935.       break;
  1936.     }
  1937.     
  1938.     if (aWindow.windowState == aWindow.STATE_NORMAL) {
  1939.       return dimension;
  1940.     }
  1941.     return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
  1942.   },
  1943.  
  1944.   /**
  1945.    * Convenience method to get localized string bundles
  1946.    * @param aURI
  1947.    * @returns nsIStringBundle
  1948.    */
  1949.   _getStringBundle: function sss_getStringBundle(aURI) {
  1950.      var bundleService = Cc["@mozilla.org/intl/stringbundle;1"].
  1951.                          getService(Ci.nsIStringBundleService);
  1952.      var appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
  1953.                      getService(Ci.nsILocaleService).getApplicationLocale();
  1954.      return bundleService.createBundle(aURI, appLocale);
  1955.   },
  1956.  
  1957.   /**
  1958.    * Get nsIURI from string
  1959.    * @param string
  1960.    * @returns nsIURI
  1961.    */
  1962.   _getURIFromString: function sss_getURIFromString(aString) {
  1963.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  1964.                     getService(Ci.nsIIOService);
  1965.     return ioService.newURI(aString, null, null);
  1966.   },
  1967.  
  1968.   /**
  1969.    * Annotate a breakpad crash report with the currently selected tab's URL.
  1970.    */
  1971.   _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) {
  1972.     if (!Ci.nsICrashReporter) {
  1973.       // if breakpad isn't built, don't bother next time at all
  1974.       this._updateCrashReportURL = function(aWindow) {};
  1975.       return;
  1976.     }
  1977.     try {
  1978.       var currentUrl = aWindow.getBrowser().currentURI.spec;
  1979.       var cr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsICrashReporter);
  1980.       cr.annotateCrashReport("URL", currentUrl);
  1981.     }
  1982.     catch (ex) {
  1983.       // don't make noise when crashreporter is built but not enabled
  1984.       if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
  1985.         debug(ex);
  1986.     }
  1987.   },
  1988.  
  1989.   /**
  1990.    * safe eval'ing
  1991.    */
  1992.   _safeEval: function sss_safeEval(aStr) {
  1993.     return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank"));
  1994.   },
  1995.  
  1996.   /**
  1997.    * Converts a JavaScript object into a JSON string
  1998.    * (see http://www.json.org/ for more information).
  1999.    *
  2000.    * The inverse operation consists of eval("(" + JSON_string + ")");
  2001.    * and should be provably safe.
  2002.    *
  2003.    * @param aJSObject is the object to be converted
  2004.    * @return the object's JSON representation
  2005.    */
  2006.   _toJSONString: function sss_toJSONString(aJSObject) {
  2007.     var str = JSON.toString(aJSObject, ["_tab", "_hosts"] /* keys to drop */);
  2008.     
  2009.     // sanity check - so that API consumers can just eval this string
  2010.     if (!JSON.isMostlyHarmless(str))
  2011.       throw new Error("JSON conversion failed unexpectedly!");
  2012.     
  2013.     return str;
  2014.   },
  2015.  
  2016. /* ........ Storage API .............. */
  2017.  
  2018.   /**
  2019.    * write file to disk
  2020.    * @param aFile
  2021.    *        nsIFile
  2022.    * @param aData
  2023.    *        String data
  2024.    */
  2025.   _writeFile: function sss_writeFile(aFile, aData) {
  2026.     // init stream
  2027.     var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  2028.                  createInstance(Ci.nsIFileOutputStream);
  2029.     stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
  2030.  
  2031.     // convert to UTF-8
  2032.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  2033.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  2034.     converter.charset = "UTF-8";
  2035.     var convertedData = converter.ConvertFromUnicode(aData);
  2036.     convertedData += converter.Finish();
  2037.  
  2038.     // write and close stream
  2039.     stream.write(convertedData, convertedData.length);
  2040.     if (stream instanceof Ci.nsISafeOutputStream) {
  2041.       stream.finish();
  2042.     } else {
  2043.       stream.close();
  2044.     }
  2045.   },
  2046.  
  2047. /* ........ QueryInterface .............. */
  2048.  
  2049.   QueryInterface: function(aIID) {
  2050.     if (!aIID.equals(Ci.nsISupports) && 
  2051.       !aIID.equals(Ci.nsIObserver) && 
  2052.       !aIID.equals(Ci.nsISupportsWeakReference) && 
  2053.       !aIID.equals(Ci.nsIDOMEventListener) &&
  2054.       !aIID.equals(Ci.nsISessionStore)) {
  2055.       Components.returnCode = Cr.NS_ERROR_NO_INTERFACE;
  2056.       return null;
  2057.     }
  2058.     
  2059.     return this;
  2060.   }
  2061. };
  2062.  
  2063. /* :::::::: Service Registration & Initialization ::::::::::::::: */
  2064.  
  2065. /* ........ nsIModule .............. */
  2066.  
  2067. const SessionStoreModule = {
  2068.  
  2069.   getClassObject: function(aCompMgr, aCID, aIID) {
  2070.     if (aCID.equals(CID)) {
  2071.       return SessionStoreFactory;
  2072.     }
  2073.     
  2074.     Components.returnCode = Cr.NS_ERROR_NOT_REGISTERED;
  2075.     return null;
  2076.   },
  2077.  
  2078.   registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) {
  2079.     aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  2080.     aCompMgr.registerFactoryLocation(CID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
  2081.  
  2082.     var catMan = Cc["@mozilla.org/categorymanager;1"].
  2083.                  getService(Ci.nsICategoryManager);
  2084.     catMan.addCategoryEntry("app-startup", CLASS_NAME, "service," + CONTRACT_ID, true, true);
  2085.   },
  2086.  
  2087.   unregisterSelf: function(aCompMgr, aLocation, aType) {
  2088.     aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  2089.     aCompMgr.unregisterFactoryLocation(CID, aLocation);
  2090.  
  2091.     var catMan = Cc["@mozilla.org/categorymanager;1"].
  2092.                  getService(Ci.nsICategoryManager);
  2093.     catMan.deleteCategoryEntry( "app-startup", "service," + CONTRACT_ID, true);
  2094.   },
  2095.  
  2096.   canUnload: function(aCompMgr) {
  2097.     return true;
  2098.   }
  2099. }
  2100.  
  2101. /* ........ nsIFactory .............. */
  2102.  
  2103. const SessionStoreFactory = {
  2104.  
  2105.   createInstance: function(aOuter, aIID) {
  2106.     if (aOuter != null) {
  2107.       Components.returnCode = Cr.NS_ERROR_NO_AGGREGATION;
  2108.       return null;
  2109.     }
  2110.     
  2111.     return (new SessionStoreService()).QueryInterface(aIID);
  2112.   },
  2113.  
  2114.   lockFactory: function(aLock) { },
  2115.  
  2116.   QueryInterface: function(aIID) {
  2117.     if (!aIID.equals(Ci.nsISupports) && !aIID.equals(Ci.nsIModule) &&
  2118.         !aIID.equals(Ci.nsIFactory) && !aIID.equals(Ci.nsISessionStore)) {
  2119.       Components.returnCode = Cr.NS_ERROR_NO_INTERFACE;
  2120.       return null;
  2121.     }
  2122.     
  2123.     return this;
  2124.   }
  2125. };
  2126.  
  2127. function NSGetModule(aComMgr, aFileSpec) {
  2128.   return SessionStoreModule;
  2129. }
  2130.